package oneD.t3dviewer2;

/* CompactModel.java  Representation of a 3D model as a wire frame of line
                      segments tailored for use in a AWT drawing environment.

   History:

   10 Jun 2008  Extracted and adapted from Model3D class in ThreeD.java.
 */

/**
 * The representation of a 3D model. Used by ThreeD.
 * 
 * @see ThreeD
 */
public class CompactModel extends Model3D {
	/**
	 * A vertex index was used which is too large to fit in the bitfield
	 * reserved for it.
	 * 
	 * @param s
	 *            description of the situation
	 * @see CompactModel#addLine
	 * @see CompactModel#M_INDEX_MASK
	 * @see CompactModel#M_INDEX_NBITS
	 */
	public class VertexOverflowException extends ModelException {
		public VertexOverflowException(String s) {
			super(s);
		}
	}

	/**
	 * Average x value (in model coordinates) for the figure.
	 */
	float m_xmean;
	/**
	 * Average y value (in model coordinates) for the figure.
	 */
	float m_ymean;
	/**
	 * Average z value (in model coordinates) for the figure.
	 */
	float m_zmean;
	/**
	 * Radius (in model coordinates) of the figure's bounding sphere.
	 */
	float m_radius;
	/**
	 * Untransformed (model) coordinates for all points.
	 */
	private float m_verts[];
	/**
	 * Size of used portion of vertex array.
	 */
	private int m_nverts;
	/**
	 * Minimum size of vertex and line arrays.
	 */
	static final int M_MIN_BUFFSIZE = 50;
	/**
	 * One integer for each line. There are two bitfields indicating the indices
	 * of the endpoint vertices (0 based index), and a third bitfield indicating
	 * a type. The latter value is used when the line is painted to indicate how
	 * it should be rendered.
	 * 
	 * @see #M_INDEX_MASK
	 * @see #M_INDEX_NBITS
	 * @see #M_TYPE_MASK
	 * @see #M_TYPE_NBITS
	 */
	private int m_lines[];
	/**
	 * Indicates how many of the values in m_lines are valid. Incremented by
	 * addLine().
	 * 
	 * @see #m_lines
	 * @see #addLine
	 */
	private int m_nlines;
	/**
	 * Mask used to extract the index bitfields from an integer containing a
	 * line's description. Used by p1(), p2() and type() to decode the integers
	 * in m_lines.
	 * 
	 * @see #m_lines
	 * @see #p1
	 * @see #p2
	 * @see #type
	 */
	private static final int M_INDEX_MASK = 0x7FFF;
	/**
	 * Bit count used to extract the index and type bitfields from an integer
	 * containing a line's description. Used by addLine() to encode and p1(),
	 * p2() and type() to decode the integers in m_lines. It should be set to
	 * the number of set bits in M_INDEX_MASK.
	 * 
	 * @see #M_INDEX_MASK
	 * @see #m_lines
	 * @see #addLine
	 * @see #p1
	 * @see #p2
	 * @see #type
	 */
	private static final int M_INDEX_NBITS = 15;
	/**
	 * Mask used to extract the type bitfield from an integer containing a
	 * line's description. Used by addLine() to encode and p1(), p2() and type()
	 * to decode the integers in m_lines.
	 * 
	 * @see #m_lines
	 * @see #addLine
	 * @see #p1
	 * @see #p2
	 * @see #type
	 */
	private static final int M_TYPE_MASK = 0x0003;
	/**
	 * Bit count used to extract the index and type bitfields from an integer
	 * containing a line's description. Used by addLine() to encode and p1(),
	 * p2() and type() to decode the integers in m_lines. It should be set to
	 * the number of set bits in M_TYPE_MASK.
	 * 
	 * @see #M_TYPE_MASK
	 * @see #m_lines
	 * @see #addLine
	 * @see #p1
	 * @see #p2
	 * @see #type
	 */
	private static final int M_TYPE_NBITS = 2;

	CompactModel() {
	}

	/**
	 * Initialize the model to the empty state.
	 */
	public void reset() {
		m_nverts = 0;
		m_nlines = 0;
	}

	/**
	 * See how many vertices are in the current model -- can also be used to
	 * check if a model has been loaded.
	 */
	int nverts() {
		return m_nverts;
	}

	/**
	 * Get vertices for the current model.
	 */
	float[] verts() {
		return m_verts;
	}

	/**
	 * See how many lines are in the current model.
	 */
	int nlines() {
		return m_nlines;
	}

	/**
	 * Index of first end point for given line.
	 */
	int p1(int index) {
		return ((m_lines[index] >> (M_INDEX_NBITS + M_TYPE_NBITS)) & M_INDEX_MASK) * 3;
	}

	/**
	 * Index of second end point for given line.
	 */
	int p2(int index) {
		return ((m_lines[index] >> M_TYPE_NBITS) & M_INDEX_MASK) * 3;
	}

	/**
	 * Type value for given line.
	 */
	int type(int index) {
		return m_lines[index] & M_TYPE_MASK;
	}

	/**
	 * Add a vertex to this model. Implements abstract method.
	 * 
	 * @param x
	 *            x coordinate of point
	 * @param y
	 *            y coordinate of point
	 * @param z
	 *            z coordinate of point
	 * @exception ModelException
	 *                See exception documentation for reason.
	 */
	void addPoint(double x, double y, double z) throws ModelException {
		int i = m_nverts;
		if (i >= M_INDEX_MASK)
			throw new VertexOverflowException(toString());
		if (m_verts == null)
			m_verts = new float[M_MIN_BUFFSIZE * 3];
		else if ((i + 1) * 3 > m_verts.length) {
			float nv[] = new float[m_verts.length * 2];
			System.arraycopy(m_verts, 0, nv, 0, m_verts.length);
			m_verts = nv;
		}
		i *= 3;
		m_verts[i] = (float) x;
		m_verts[i + 1] = (float) y;
		m_verts[i + 2] = (float) z;
		m_nverts++;
	}

	/**
	 * Add a line from vertex p1 to vertex p2 to the model's geometry.
	 * Implements abstract method.
	 * 
	 * @param p1
	 *            zero-based index of first endpoint
	 * @param p2
	 *            zero-based index of second endpoint
	 * @param type
	 *            type value for this line segment
	 * @exception ModelException
	 *                See exception documentation for reason.
	 */
	void addLineSegment(int p1, int p2, int val) {
		val = val % 8;
		int type = 0;
		if (val == 0)
			return;
		else if (val == 1)
			type = 1;
		else if (val == 2 || val == 4 || val == 6)
			type = 2;
		else if (val == 3 || val == 5)
			type = 3;
		else
			type = 0;
		if (p1 >= m_nverts || p2 >= m_nverts)
			return;
		if (m_lines == null)
			m_lines = new int[M_MIN_BUFFSIZE];
		else if (m_nlines >= m_lines.length) {
			int nl[] = new int[m_lines.length * 2];
			System.arraycopy(m_lines, 0, nl, 0, m_lines.length);
			m_lines = nl;
		}
		m_lines[m_nlines] = (p1 << (M_INDEX_NBITS + M_TYPE_NBITS))
				| (p2 << M_TYPE_NBITS) | (type & M_TYPE_MASK);
		m_nlines++;
	}

	/**
	 * Compute the bounding sphere (in model coordinates) of this model.
	 * Implements abstract method.
	 * 
	 * @see #m_xmean
	 * @see #m_ymean
	 * @see #m_zmean
	 * @see #m_radius
	 */
	void computeBoundingSphere() {
		if (m_nverts <= 0)
			return;

		// find center
		m_xmean = 0;
		m_ymean = 0;
		m_zmean = 0;
		for (int i = (m_nverts - 1) * 3; i >= 0; i -= 3) {
			m_xmean += m_verts[i];
			m_ymean += m_verts[i + 1];
			m_zmean += m_verts[i + 2];
		}
		m_xmean /= m_nverts;
		m_ymean /= m_nverts;
		m_zmean /= m_nverts;

		// find radius
		m_radius = 0;
		for (int i = (m_nverts - 1) * 3; i >= 0; i -= 3) {
			float dx = m_verts[i] - m_xmean;
			float dy = m_verts[i + 1] - m_ymean;
			float dz = m_verts[i + 2] - m_zmean;
			float radius = dx * dx + dy * dy + dz * dz;
			if (radius > m_radius)
				m_radius = radius;
		}
		m_radius = (float) Math.sqrt(m_radius);
	}
}
